﻿/*
 * Copyright (c) 2011
 * Thomas Rozanski, Geoffrey Wright, Zachary Lynn, Jesse Natalie
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the team nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THOMAS ROZANSKI, GEOFFREY WRIGHT,
 * ZACHARY LYNN, OR JESSE NATALIE BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using Microsoft.Xna.Framework;
using Kinected;

namespace Pinball
{
    /// <summary>Movement and collision logic for an on-screen ball.</summary>
    class Ball
    {
        private Circle bounds;
        private Random random;

        private Vector2 origin, position, velocity;

        public Vector2 Origin { get { return origin; } }
        public Circle Bounds { get { return bounds; } }
        public Vector2 Position { get { return position; } }

        public Ball(Vector2 position, float radius, Vector2 velocity)
        {
            random = new Random();

            this.position = position;
            this.velocity = velocity;

            origin = new Vector2(radius);

            bounds = new Circle(position, 0.0f, radius);
        }

        /// <summary>Handles a horizontal collision (top of a level)</summary>
        public void HorizontalCollision(int wallY)
        {
            // Hits the wall downward
            if (velocity.Y > 0)
            {
                position.Y = wallY - bounds.Radius * 2;
            }
            else
            {
                position.Y = wallY;
            }

            // Change velocity
            velocity.Y = (-velocity.Y + random.Next(-15, 15));
        }

        /// <summary>Handles a vertical collision (side of a level)</summary>
        public void VerticalCollision(int wallX)
        {
            // Hits the right wall
            if (velocity.X > 0)
            {
                position.X = wallX - bounds.Radius * 2;
            }
            else
            {
                position.X = wallX;
            }

            // Change velocity
            velocity.X = (-velocity.X + random.Next(-15, 15));
        }

        /// <summary>Performs an elastic collision between two balls.</summary>
        public void ElasticCollision(Ball otherBall, float timeStep)
        {
            // Compute the vector between the two
            Vector2 change = otherBall.Position - Position;
            float distance = Vector2.Distance(Position, otherBall.Position);

            float vp1 = velocity.X * change.X / distance + velocity.Y * change.Y / distance;
            float vp2 = otherBall.velocity.X + change.X / distance + otherBall.velocity.Y * change.Y / distance;

            float dt = (Bounds.Radius + otherBall.Bounds.Radius - distance) / (vp1 - vp2);

            // Fix the distance of balls to be exactly r1 + r2 away
            position -= velocity * dt;
            otherBall.position -= otherBall.velocity * dt;

            // Turn change into unit vector
            change.Normalize();

            float va1 = Vector2.Dot(velocity, change);
            float va2 = Vector2.Dot(otherBall.velocity, change);

            float vb1 = -velocity.X * change.Y + velocity.Y * change.X;
            float vb2 = -otherBall.velocity.X * change.Y + otherBall.velocity.Y * change.X;

            float vaP1 = va1 + (va2 - va1);
            float vaP2 = va2 + (va1 - va2);

            velocity.X = vaP1 * change.X - vb1 * change.Y;
            velocity.Y = vaP1 * change.Y + vb1 * change.X;

            otherBall.velocity.X = vaP2 * change.X - vb2 * change.Y;
            otherBall.velocity.Y = vaP2 * change.Y + vb2 * change.X;

            // Add what velocity was removed
            position += velocity * dt;
            otherBall.position += otherBall.velocity * dt;
        }

        /// <summary>Handles a direct collision with a bumper.</summary>
        public void BumperCollision(Bumper bumper, float timeStep)
        {
            // Constants
            const float bumperForce = 100f, maxSpeed = 250f;

            // The normal from the circle is simply the vector from bumper to ball
            Vector2 normal = bounds.Center - bumper.Bounds.Center;
            normal.Normalize();

            // Reflect a velocity vector around a normal: r = u - 2(u dot n)n
            Vector2 reflected = velocity - 2 * Vector2.Dot(velocity, normal) * normal;

            // Apply a force from the bumper and clamp it at max speed
            Vector2 final = reflected + normal * bumperForce;
            if (final.Length() >= maxSpeed)
            {
                final.Normalize();
                final *= maxSpeed;
            }

            // Final velocity is now accurate
            velocity = final;

            Update(timeStep * 10f);
        }

        /// <summary>Updates the ball.</summary>
        public void Update(float timeStep)
        {
            // Upate the circle position
            position += velocity * timeStep;

            // Update the bounds
            bounds.Center = position + origin;
        }
    }
}
